/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#include <type_traits>

#include "module.h"

using namespace std;

using namespace metal;
using namespace metal::meta;

namespace {
  template<typename T>
  void check(T t) {
    static_assert(!is_void<T>::value,
		  "illegal type");
  }

  module<> test_module("");

  struct x { };
  struct y { };
  struct z { };

  typedef parameter<int, y> int_y;
  typedef parameter<long, z> long_z;

#define nullary_decl short, x
#define unary_decl short, x, int_y
#define binary_decl short, x, int_y, long_z

  assignment<
    true, 
    x, 
    lambda_expr<void, constant_expr<short> > 
    > *nullary_assign;

  assignment<
    true, 
    x, 
    lambda_expr<void, constant_expr<short>, int_y> 
    > *unary_assign;

  assignment<
    true,
    x,
    lambda_expr<void, constant_expr<short>, int_y, long_z> 
    > *binary_assign;

  // definition conversion
  typedef bindings<> nil;

  typedef module_declaration_binder<meta::bind<x, list<short>, nil>::type, nil, nullary_decl> null_null;  
  static_assert(null_null::compatible, "should accept decl x(), decl x()");

  typedef module_declaration_binder<meta::bind<x, list<short>, nil>::type, nil, unary_decl> null_un;  
  static_assert(!null_un::compatible, "should not accept decl x(), decl x(int)");

  typedef module_declaration_binder<meta::bind<x, list<short>, nil>::type, nil, binary_decl> null_bin;  
  static_assert(!null_bin::compatible, "should not accept decl x(), decl x(int, long)");


  typedef module_declaration_binder<meta::bind<x, list<short, int>, nil>::type, nil, nullary_decl> un_null;  
  static_assert(!un_null::compatible, "should not accept decl x(int), decl x()");

  typedef module_declaration_binder<meta::bind<x, list<short, int>, nil>::type, nil, unary_decl> un_un;  
  static_assert(un_un::compatible, "should accept decl x(int), decl x(int)");

  typedef module_declaration_binder<meta::bind<x, list<short, int>, nil>::type, nil, binary_decl> un_bin;  
  static_assert(!un_bin::compatible, "should not accept decl x(int), decl x(int, long)");


  typedef module_declaration_binder<meta::bind<x, list<short, int, long>, nil>::type, nil, nullary_decl> bin_null;  
  static_assert(!bin_null::compatible, "should not accept decl x(int, long), decl x()");

  typedef module_declaration_binder<meta::bind<x, list<short, int, long>, nil>::type, nil, unary_decl> bin_un;  
  static_assert(!bin_un::compatible, "should not accept decl x(int, long), decl x(int)");

  typedef module_declaration_binder<meta::bind<x, list<short, int, long>, nil>::type, nil, binary_decl> bin_bin;  
  static_assert(bin_bin::compatible, "should accept decl x(int, long), decl x(int, long)");
  
  static_assert(is_same<module_declaration_binder<nil, nil, nullary_decl>::type, 
		module<bindings<binding<x, list<short> > >, nil> >::value, 
		"did not bind x");
  
  

  static_assert(is_same<decltype(test_module, *nullary_assign),
		module<bindings<binding<x, list<short> > >, set<x> > >::value, 
		"did not bind x with ,");
  
  static_assert(is_same<decltype(test_module, *nullary_assign, *unary_assign),
		module<bindings<binding<x, list<short, int> >, binding<x, list<short> > >, set<x, x> > >::value, 
		"did not double-bind x with ,");
  
  static_assert(is_same<decltype(test_module, *nullary_assign, *unary_assign, *binary_assign),
		module<bindings<binding<x, list<short, int, long> >, binding<x, list<short, int> >, binding<x, list<short> > >, set<x, x, x> > >::value, 
		"did not triple-bind x (with ,)");


  // does remember definition

#define nullary_def x, constant_expr<short>
  
  typedef module_definition_binder<bindings<>, set<>, nullary_def> ndef_binder;
  
  static_assert(is_same<ndef_binder::type, module<bindings<binding<x, list<short> > >, set<x> > >::value,
		"definition did not set the correct meta-module type");
  static_assert(ndef_binder::compatible, "first-time definition is always compatible with previous decls");
  static_assert(!ndef_binder::duplicate, "first-time definition is not a duplicate definition");
}
